/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
 * (c) 2001 - 2013 OpenPlans
 * This code is licensed under the GPL 2.0 license, available at the root
 * application directory.
 */
package org.geoserver.security.decorators;

import java.util.logging.Logger;
import org.geoserver.catalog.CoverageInfo;
import org.geoserver.catalog.CoverageStoreInfo;
import org.geoserver.catalog.DataStoreInfo;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.WMSLayerInfo;
import org.geoserver.catalog.WMSStoreInfo;
import org.geoserver.catalog.WMTSLayerInfo;
import org.geoserver.catalog.WMTSStoreInfo;
import org.geoserver.catalog.impl.ModificationProxy;
import org.geoserver.platform.ExtensionPriority;
import org.geoserver.security.WrapperPolicy;
import org.geotools.util.logging.Logging;

/**
 * Creates security wrappers for the most common catalog objects
 *
 * @author Andrea Aime - TOPP
 */
public class DefaultSecureCatalogFactory implements SecuredObjectFactory {

    private static final Logger LOGGER = Logging.getLogger(DefaultSecureCatalogFactory.class);

    @Override
    public boolean canSecure(Class<?> clazz) {
        return CoverageInfo.class.isAssignableFrom(clazz)
                || CoverageStoreInfo.class.isAssignableFrom(clazz)
                || DataStoreInfo.class.isAssignableFrom(clazz)
                || FeatureTypeInfo.class.isAssignableFrom(clazz)
                || LayerInfo.class.isAssignableFrom(clazz)
                || WMSLayerInfo.class.isAssignableFrom(clazz)
                || WMTSLayerInfo.class.isAssignableFrom(clazz)
                || WMSStoreInfo.class.isAssignableFrom(clazz)
                || WMTSStoreInfo.class.isAssignableFrom(clazz);
    }

    @Override
    public Object secure(Object object, WrapperPolicy policy) {
        // null safe
        if (object == null) return null;

        Class<?> clazz = object.getClass();
        // for each supported Info type, log a warning if the object to be secured is already
        // secured. If this happens,
        // it could lead to a StackOverflowError if the object is re-wrapped, over time, over and
        // over agian.
        if (CoverageInfo.class.isAssignableFrom(clazz))
            return new SecuredCoverageInfo(logIfSecured((CoverageInfo) object), policy);
        else if (CoverageStoreInfo.class.isAssignableFrom(clazz))
            return new SecuredCoverageStoreInfo(logIfSecured((CoverageStoreInfo) object), policy);
        else if (DataStoreInfo.class.isAssignableFrom(clazz))
            return new SecuredDataStoreInfo(logIfSecured((DataStoreInfo) object), policy);
        else if (FeatureTypeInfo.class.isAssignableFrom(clazz))
            return new SecuredFeatureTypeInfo(logIfSecured((FeatureTypeInfo) object), policy);
        else if (LayerInfo.class.isAssignableFrom(clazz))
            return new SecuredLayerInfo(logIfSecured((LayerInfo) object), policy);
        else if (WMSLayerInfo.class.isAssignableFrom(clazz))
            return new SecuredWMSLayerInfo(logIfSecured((WMSLayerInfo) object), policy);
        else if (WMTSLayerInfo.class.isAssignableFrom(clazz))
            return new SecuredWMTSLayerInfo(logIfSecured((WMTSLayerInfo) object), policy);
        else if (WMSStoreInfo.class.isAssignableFrom(clazz))
            return new SecuredWMSStoreInfo(logIfSecured((WMSStoreInfo) object), policy);
        else if (WMTSStoreInfo.class.isAssignableFrom(clazz))
            return new SecuredWMTSStoreInfo(logIfSecured((WMTSStoreInfo) object), policy);
        else throw new IllegalArgumentException("Don't know how to wrap " + object);
    }
    /**
     * Returns {@link ExtensionPriority#LOWEST} since the wrappers generated by this factory
     *
     * @return {@link ExtensionPriority#LOWEST}
     */
    @Override
    public int getPriority() {
        return ExtensionPriority.LOWEST;
    }

    private void logDoubleWrap(Object unwrapped, Object orig) {
        String msg =
                String.format("Tried to double secure: %s already securing %s", orig, unwrapped);
        LOGGER.warning(msg);
    }

    /**
     * Generates a warning log if the Info object is already wrapped with a Secured decorator. This
     * method is only intended to log a situation where a Catalog Info object is being secured, but
     * is already secured. Repeated calls to this will keep adding additional wrapper layers and may
     * eventually cause a StackOverflowError. The log generated is merely to aid in finding the real
     * issue, as opposed to masking it here.
     *
     * @param object {@link WMTSLayerInfo} to check.
     * @return The original object to be checked.
     */
    private WMTSLayerInfo logIfSecured(WMTSLayerInfo object) {
        WMTSLayerInfo unwrapped = ModificationProxy.unwrap(object);
        if (unwrapped instanceof SecuredWMTSLayerInfo) {
            logDoubleWrap(unwrapped, object);
        }
        return object;
    }

    /**
     * Generates a warning log if the Info object is already wrapped with a Secured decorator. This
     * method is only intended to log a situation where a Catalog Info object is being secured, but
     * is already secured. Repeated calls to this will keep adding additional wrapper layers and may
     * eventually cause a StackOverflowError. The log generated is merely to aid in finding the real
     * issue, as opposed to masking it here.
     *
     * @param object {@link WMSLayerInfo} to check.
     * @return The original object to be checked.
     */
    private WMSLayerInfo logIfSecured(WMSLayerInfo object) {
        WMSLayerInfo unwrapped = ModificationProxy.unwrap(object);
        if (unwrapped instanceof SecuredWMSLayerInfo) {
            logDoubleWrap(unwrapped, object);
        }
        return object;
    }

    /**
     * Generates a warning log if the Info object is already wrapped with a Secured decorator. This
     * method is only intended to log a situation where a Catalog Info object is being secured, but
     * is already secured. Repeated calls to this will keep adding additional wrapper layers and may
     * eventually cause a StackOverflowError. The log generated is merely to aid in finding the real
     * issue, as opposed to masking it here.
     *
     * @param object {@link LayerInfo} to check.
     * @return The original object to be checked.
     */
    private LayerInfo logIfSecured(LayerInfo object) {
        LayerInfo unwrapped = ModificationProxy.unwrap(object);
        if (unwrapped instanceof SecuredLayerInfo) {
            logDoubleWrap(unwrapped, object);
        }
        return object;
    }

    /**
     * Generates a warning log if the Info object is already wrapped with a Secured decorator. This
     * method is only intended to log a situation where a Catalog Info object is being secured, but
     * is already secured. Repeated calls to this will keep adding additional wrapper layers and may
     * eventually cause a StackOverflowError. The log generated is merely to aid in finding the real
     * issue, as opposed to masking it here.
     *
     * @param object {@link FeatureTypeInfo} to check.
     * @return The original object to be checked.
     */
    private FeatureTypeInfo logIfSecured(FeatureTypeInfo object) {
        FeatureTypeInfo unwrapped = ModificationProxy.unwrap(object);
        if (unwrapped instanceof SecuredFeatureTypeInfo) {
            logDoubleWrap(unwrapped, object);
        }
        return object;
    }

    /**
     * Generates a warning log if the Info object is already wrapped with a Secured decorator. This
     * method is only intended to log a situation where a Catalog Info object is being secured, but
     * is already secured. Repeated calls to this will keep adding additional wrapper layers and may
     * eventually cause a StackOverflowError. The log generated is merely to aid in finding the real
     * issue, as opposed to masking it here.
     *
     * @param object {@link CoverageStoreInfo} to check.
     * @return The original object to be checked.
     */
    private CoverageStoreInfo logIfSecured(CoverageStoreInfo object) {
        CoverageStoreInfo unwrapped = ModificationProxy.unwrap(object);
        if (unwrapped instanceof SecuredCoverageStoreInfo) {
            logDoubleWrap(unwrapped, object);
        }
        return object;
    }

    /**
     * Generates a warning log if the Info object is already wrapped with a Secured decorator. This
     * method is only intended to log a situation where a Catalog Info object is being secured, but
     * is already secured. Repeated calls to this will keep adding additional wrapper layers and may
     * eventually cause a StackOverflowError. The log generated is merely to aid in finding the real
     * issue, as opposed to masking it here.
     *
     * @param object {@link CoverageInfo} to check.
     * @return The original object to be checked.
     */
    private CoverageInfo logIfSecured(CoverageInfo object) {
        CoverageInfo unwrapped = ModificationProxy.unwrap(object);
        if (unwrapped instanceof SecuredDataStoreInfo) {
            logDoubleWrap(unwrapped, object);
        }
        return object;
    }

    /**
     * Generates a warning log if the Info object is already wrapped with a Secured decorator. This
     * method is only intended to log a situation where a Catalog Info object is being secured, but
     * is already secured. Repeated calls to this will keep adding additional wrapper layers and may
     * eventually cause a StackOverflowError. The log generated is merely to aid in finding the real
     * issue, as opposed to masking it here.
     *
     * @param object {@link DataStoreInfo} to check.
     * @return The original object to be checked.
     */
    private DataStoreInfo logIfSecured(DataStoreInfo object) {
        DataStoreInfo unwrapped = ModificationProxy.unwrap(object);
        if (unwrapped instanceof SecuredDataStoreInfo) {
            logDoubleWrap(unwrapped, object);
        }
        return object;
    }

    /**
     * Generates a warning log if the Info object is already wrapped with a Secured decorator. This
     * method is only intended to log a situation where a Catalog Info object is being secured, but
     * is already secured. Repeated calls to this will keep adding additional wrapper layers and may
     * eventually cause a StackOverflowError. The log generated is merely to aid in finding the real
     * issue, as opposed to masking it here.
     *
     * @param object {@link WMSStoreInfo} to check.
     * @return The original object to be checked.
     */
    private WMSStoreInfo logIfSecured(WMSStoreInfo object) {
        WMSStoreInfo unwrapped = ModificationProxy.unwrap(object);
        if (unwrapped instanceof SecuredDataStoreInfo) {
            logDoubleWrap(unwrapped, object);
        }
        return object;
    }

    /**
     * Generates a warning log if the Info object is already wrapped with a Secured decorator. This
     * method is only intended to log a situation where a Catalog Info object is being secured, but
     * is already secured. Repeated calls to this will keep adding additional wrapper layers and may
     * eventually cause a StackOverflowError. The log generated is merely to aid in finding the real
     * issue, as opposed to masking it here.
     *
     * @param object {@link WMTSStoreInfo} to check.
     * @return The original object to be checked.
     */
    private WMTSStoreInfo logIfSecured(WMTSStoreInfo object) {
        WMTSStoreInfo unwrapped = ModificationProxy.unwrap(object);
        if (unwrapped instanceof SecuredDataStoreInfo) {
            logDoubleWrap(unwrapped, object);
        }
        return object;
    }
}
